# MemberPrinter.java

    package org.codefilarete.tool.reflect;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.Executable;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Set;
    import java.util.StringTokenizer;

    import org.codefilarete.tool.StringAppender;
    import org.codefilarete.tool.collection.Arrays;
    import org.codefilarete.tool.exception.NotImplementedException;
    import org.codefilarete.tool.Nullable;

    /**
     * A class for custom Reflection API print. Mainly for compact package presentation.
     *
     * @author Guillaume Mary
     */
    public class MemberPrinter {

    	/** A printer that shows the whole package name (full path) */
    	public static final MemberPrinter FULL_PACKAGE_PRINTER = new MemberPrinter(Package::getName);

    	/** A printer for compact package name presentation (first letter of each path) */
    	public static final MemberPrinter FLATTEN_PACKAGE_PRINTER = new MemberPrinter(MemberPrinter::flattenPackage);

    	/** A printer that won't print package name for "well known" package : java.util and java.lang */
    	public static final MemberPrinter WELL_KNOWN_FLATTEN_PACKAGE_PRINTER = new MemberPrinter(new PackagePrinter() {

    		private final Set<Package> WELL_KNOW_PACKAGES = Collections.unmodifiableSet(Arrays.asSet(
    				Collection.class.getPackage(),
    				String.class.getPackage()));

    		@Override
    		public String toString(Package aPackage) {
    			if (WELL_KNOW_PACKAGES.contains(aPackage)) {
    				// well-known packages are not printed
    				return "";
    			} else {
    				return FLATTEN_PACKAGE_PRINTER.toString(aPackage);
    			}
    		}
    	});

    	/**
    	 * Gives a compact view of a package name by keeping only first character of each path, separated by dots.
    	 *
    	 * @param aPackage not null (not compatible with top level classes / primitive types)
    	 * @return a String of the first character of package names
    	 */
    	private static String flattenPackage(Package aPackage) {
    		StringAppender result = new StringAppender();
    		StringTokenizer tokenizer = new StringTokenizer(aPackage.getName(), ".", false);
    		while (tokenizer.hasMoreTokens()) {
    			String packageName = tokenizer.nextToken();
    			result.cat(packageName.charAt(0), ".");
    		}
    		result.cutTail(1);
    		return result.toString();
    	}

    	private final PackagePrinter packagePrinter;

    	public MemberPrinter(PackagePrinter packagePrinter) {
    		this.packagePrinter = packagePrinter;
    	}

    	public String toString(Package aPackage) {
    		// prevent top level class case
    		return aPackage == null ? "" : packagePrinter.toString(aPackage);
    	}

    	public String toString(Class aClass) {
    		Package classPackage = aClass.isArray() ? aClass.getComponentType().getPackage() : aClass.getPackage();
    		String packageName = toString(classPackage);
    		if (packageName.isEmpty()) {
    			return aClass.getSimpleName();
    		} else {
    			return packageName + "." + Nullable.nullable(aClass.getEnclosingClass()).map(c -> c.getSimpleName() + "$").getOr("") + aClass.getSimpleName();
    		}
    	}

    	public String toString(Constructor constructor) {
    		return new ClassAppender().cat(constructor.getDeclaringClass())
    				.cat("(").ccat(constructor.getParameterTypes(), ", ").cat(")").toString();
    	}

    	public String toString(Method method) {
    		return new ClassAppender().cat(method.getDeclaringClass(), ".", method.getName())
    				.cat("(").ccat(method.getParameterTypes(), ", ").cat(")").toString();
    	}

    	public String toString(Executable executable) {
    		if (executable instanceof Constructor) {
    			return toString((Constructor) executable);
    		} else if (executable instanceof Method) {
    			return toString((Method) executable);
    		} else {
    			throw new NotImplementedException(Nullable.nullable(executable).map(Object::getClass).map(this::toString).getOr("null instance") + " is not supported");
    		}
    	}

    	public String toString(Field field) {
    		return new ClassAppender().cat(field.getDeclaringClass(), ".", field.getName()).toString();
    	}

    	@FunctionalInterface
    	public interface PackagePrinter {

    		/**
    		 * Gives a representation of a package
    		 * @param aPackage not null
    		 * @return not null, an empty String in worst case
    		 */
    		String toString(Package aPackage);
    	}

    	private class ClassAppender extends StringAppender {
    		@Override
    		public StringAppender cat(Object o) {
    			if (o instanceof Class) {
    				return super.cat(MemberPrinter.this.toString((Class) o));
    			} else {
    				return super.cat(o);
    			}
    		}
    	}
    }